package com.saasovation.agilepm.port.adapter.spring;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.saasovation.agilepm.application.notification.NotificationApplicationService;
import com.saasovation.agilepm.application.process.ProcessApplicationService;
import com.saasovation.agilepm.application.product.ProductApplicationService;
import com.saasovation.agilepm.application.product.backlogitem.BacklogItemApplicationService;
import com.saasovation.agilepm.application.sprint.SprintApplicationService;
import com.saasovation.agilepm.application.team.TeamApplicationService;
import com.saasovation.agilepm.domain.model.product.ProductRepository;
import com.saasovation.agilepm.domain.model.product.backlogitem.BacklogItemRepository;
import com.saasovation.agilepm.domain.model.product.sprint.SprintRepository;
import com.saasovation.agilepm.domain.model.team.ProductOwnerRepository;
import com.saasovation.agilepm.domain.model.team.TeamMemberRepository;
import com.saasovation.common.domain.model.process.TimeConstrainedProcessTrackerRepository;
import com.saasovation.common.event.EventStore;
import com.saasovation.common.notification.NotificationPublisher;
import com.saasovation.common.notification.PublishedNotificationTrackerStore;
import com.saasovation.common.port.adapter.messaging.Exchanges;
import com.saasovation.common.port.adapter.messaging.rabbitmq.ConnectionSettings;
import com.saasovation.common.port.adapter.notification.SpringRabbitMQNotificationPublisher;
import com.saasovation.common.port.adapter.persistence.leveldb.LevelDBEventStore;
import com.saasovation.common.port.adapter.persistence.leveldb.LevelDBPublishedNotificationTrackerStore;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.JacksonUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration
public class ApplicationConfiguration {

    @Bean
    public NotificationApplicationService notificationApplicationService(
            NotificationPublisher notificationPublisher) {
        return new NotificationApplicationService(notificationPublisher);
    }

    @Bean
    public ProcessApplicationService processApplicationService(
            TimeConstrainedProcessTrackerRepository processTrackerRepository) {
        return new ProcessApplicationService(processTrackerRepository);
    }

    @Bean
    public ProductApplicationService productApplicationService(ProductRepository aProductRepository,
                                                               ProductOwnerRepository aProductOwnerRepository,
                                                               TimeConstrainedProcessTrackerRepository aProcessTrackerRepository) {
        return new ProductApplicationService(aProductRepository,
                aProductOwnerRepository,
                aProcessTrackerRepository);
    }

    @Bean
    public BacklogItemApplicationService backlogItemApplicationService(BacklogItemRepository backlogItemRepository) {
        return new BacklogItemApplicationService(backlogItemRepository);
    }

    @Bean
    public SprintApplicationService sprintApplicationService(SprintRepository aSprintRepository,
                                                             BacklogItemRepository aBacklogItemRepository) {
        return new SprintApplicationService(aSprintRepository,
                aBacklogItemRepository);
    }

    @Bean
    public TeamApplicationService teamApplicationService(TeamMemberRepository aTeamMemberRepository,
                                                         ProductOwnerRepository aProductOwnerRepository) {
        return new TeamApplicationService(aTeamMemberRepository,
                aProductOwnerRepository);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        ObjectMapper objectMapper = JacksonUtils.enhancedObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
        return rabbitTemplate;
    }

    @Bean
    public ConnectionSettings connectionSettings(Environment environment) {
        return ConnectionSettings.instance(
                environment.getProperty("spring.rabbitmq.host", "localhost"),
                Integer.parseInt(environment.getProperty("spring.rabbitmq.port", "5672")),
                environment.getProperty("spring.rabbitmq.virtual-host", "/"),
                environment.getProperty("spring.rabbitmq.username"),
                environment.getProperty("spring.rabbitmq.password"));
    }

    @Bean
    public FanoutExchange collaborationExchange() {
        return new FanoutExchange(Exchanges.COLLABORATION_EXCHANGE_NAME);
    }

    @Bean
    public FanoutExchange agilePmExchange() {
        return new FanoutExchange(Exchanges.AGILE_PM_EXCHANGE_NAME);
    }

    @Bean
    public FanoutExchange identityAccessExchange() {
        return new FanoutExchange(Exchanges.IDENTITY_ACCESS_EXCHANGE_NAME);
    }

    @Bean
    public NotificationPublisher notificationPublisher(EventStore eventStore,
                                                       PublishedNotificationTrackerStore notificationTrackerStore,
                                                       RabbitTemplate rabbitTemplate) {
        return new SpringRabbitMQNotificationPublisher(eventStore,
                notificationTrackerStore,
                rabbitTemplate,
                Exchanges.IDENTITY_ACCESS_EXCHANGE_NAME);
    }

    @Bean
    public EventStore eventStore() {
        return new LevelDBEventStore("data/leveldb/iddd_event_store");
    }

    @Bean
    public PublishedNotificationTrackerStore publishedNotificationTrackerStore() {
        return new LevelDBPublishedNotificationTrackerStore("data/leveldb/iddd_tracker_store",
                "agilePm");
    }
}
